﻿using AiMoInGH.Component;
using AiMoInGH.Pipeline;
using AiMoInGH.PrinterForm;
using GH_IO.Serialization;
using Grasshopper.GUI;
using Grasshopper.GUI.Canvas;
using Grasshopper.Kernel;
using Rhino;
using Rhino.Geometry;
using System;
using System.Drawing;
using System.IO;
using System.Linq;

namespace AiMoInGH.Connector
{
    class ScriptRunner : IDisposable
    {

        public GHScriptSource CurrentScript { get; private set; } = new GHScriptSource();
        string m_CurrentDllFile = "";
        public string CurrentDllFilePath => m_CurrentDllFile;

        GHAiMoExecutor m_Executor = null;
        PipeServer m_PipeServer = null;
        readonly AiMoInGHComponent owner;
        readonly ScriptDllLoader dllLoader = new ScriptDllLoader();
        public PrinterManager Printer { get; } = new PrinterManager();
        public ScriptRunner(AiMoInGHComponent owner)
        {
            this.owner = owner;
        }


        /// <summary>
        /// 双击时打开AiMoEditWindow
        /// 需要传过去当前的AiMo代码
        /// </summary>
        public void ShowAiMoEditWindow()
        {
            if (!AiMoRhinoLoader.HasRhinoAiMoInstalled(out string aimoExePath))
            {
                owner.AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Cannot find AiMo Editor (AiMoWindows.exe), Please install AiMo Script Editor for Rhino.");
                return;
            }
            if (m_PipeServer == null || !m_PipeServer.Connected)
            {
                m_PipeServer?.Dispose();
                m_PipeServer = new PipeServer();
                m_PipeServer.OpenNewAiMoWindow(aimoExePath, new GHExecutorInfo(this));
            }
            else
            {
                Eto.Forms.MessageBox.Show("AiMo Window of this component already started");
                // AiMo Already Started
            }
        }
        // 当代码改变时，会有新的Param设置
        public bool GetNewScriptParamInfo(out ParamInfo[] inputs, out ParamInfo[] outputs)
        {
            if (m_Executor == null)
            {

                outputs = Array.Empty<ParamInfo>();
                inputs = Array.Empty<ParamInfo>();
                return false;
            }

            inputs = m_Executor.Inputs;
            outputs = m_Executor.Outputs;
            return true;
        }

        public bool OnAiMoEditWindowRun(string newDllPath, GHScriptSource newScript, out string error)
        {

            m_CurrentDllFile = newDllPath;
            CurrentScript = newScript;

            if (owner.InSolution())
            {
                error = "Cannot change scripts when Grasshopper is expiring solution";
                return false;
            }
            if (!LoadDll(out error))
            {
                return false;
            }
            RhinoApp.InvokeOnUiThread(new Action(() =>
            {
                owner.RebuildIcon();
                owner.VariableParameterMaintenance();
                owner.ExpireSolution(true);
            }));
            return true;
        }

        /// <summary>
        /// 在Read结束之后。
        /// 也可以考虑放在OnAddToDocument？？？？？
        /// </summary>
        bool LoadDll(out string error)
        {
            error = "";
            if (m_Executor != null && m_Executor.CurrentDllPath != m_CurrentDllFile)
            {
                m_Executor.Dispose();
                m_Executor = null;
            }

            dllLoader.LoadExternalAssemblies(CurrentScript.ExternalAssembliesPaths, out string errorLoadExternal);
            if (m_Executor == null)
            {
                m_Executor = dllLoader.LoadScriptDll(m_CurrentDllFile, CurrentScript, out error);
            }
            if (!string.IsNullOrEmpty(error))
            {
                error = errorLoadExternal + "\r\n" + error;
            }
            else
            {
                error = errorLoadExternal;
            }
            if (m_Executor != null)
            {
                m_Executor.CurrentInstance.SetComponent(owner, Printer);
                m_Executor.Onload();
            }
            return m_Executor != null;
        }

        bool m_InitDataAccess = false;
        public void BeforeSolveInstance()
        {
            m_InitDataAccess = false;
            Printer.Clear();
            m_Executor?.CurrentInstance.ResetBaseDrawing();
            if (m_Executor != null)
            {
                m_Executor.CurrentInstance.BeforeSolveInstance();
                m_Executor.CurrentInstance.ClearShow();
            }
        }

        public void AfterSolveInstance()
        {
            m_Executor?.CurrentInstance.AfterSolveInstance();
            Printer.DoSync();
        }

        public void SolveInstance(IGH_DataAccess DA)
        {
            if (m_Executor != null)
            {
                if (!m_InitDataAccess)
                {
                    m_InitDataAccess = true;
                    m_Executor.CurrentInstance.SetDataAccess(
                        DA,
                        m_Executor.Inputs
                        .Select(x => x.ParamName).ToArray(),
                        m_Executor.Outputs
                        .Select(x => x.ParamName).ToArray()
                        );
                }
                m_Executor.CurrentInstance.SolveInstance();
            }
        }

        public BoundingBox ClippingBox => m_Executor?.CurrentInstance.ClippingBox ?? BoundingBox.Empty;

        public bool DrawViewportMeshesBase => m_Executor?.CurrentInstance.DrawViewportMeshesBase ?? true;
        public bool DrawViewportWiresBase => m_Executor?.CurrentInstance.DrawViewportWiresBase ?? true;
        public void DrawViewportMeshes(IGH_PreviewArgs args)
        {
            m_Executor?.CurrentInstance.DrawViewportMeshes(args);
        }
        public void DrawViewportWires(IGH_PreviewArgs args)
        {
            m_Executor?.CurrentInstance.DrawViewportWires(args);
        }
        public Bitmap Icon => m_Executor?.CurrentInstance.Icon ?? Properties.Resources.Logo24;
        public void Dispose()
        {
            m_Executor?.Dispose();
            m_Executor = null;
            m_PipeServer?.Dispose();
            m_PipeServer = null;
            dllLoader.Dispose();
        }
        public void Write(GH_IWriter writer)
        {
            writer.SetDouble("ScriptIOVersion", GHScriptSource.IOVersion);

            CurrentScript.Write(writer);

            writer.SetString("DllPath", m_CurrentDllFile);

            if (!string.IsNullOrEmpty(m_CurrentDllFile) && File.Exists(m_CurrentDllFile))
            {
                try
                {
                    var bytes = File.ReadAllBytes(m_CurrentDllFile);
                    writer.SetByteArray("CurrentDllFile", bytes);
                }
                catch (Exception ex)
                {
                    RhinoApp.WriteLine("AiMo GH Component Write Error (ReadAllBytes)" + ex.Message);
                }
            }
        }
        public bool Read(GH_IReader reader, out string error)
        {
            double version = reader.GetDouble("ScriptIOVersion");
            if (version > GHScriptSource.IOVersion)
            {
                error = "Cannot read new version AiMoScript, please update aimo";
                return false;
            }
            try
            {
                CurrentScript.Read(reader);

                string path = reader.GetString("DllPath");
                m_CurrentDllFile = path;

                if (!string.IsNullOrEmpty(path))
                {
                    //上一个GH电池里包含了Dll
                    if (!File.Exists(path))
                    {
                        string fileName = Path.GetFileName(path);
                        string directory = Path.GetDirectoryName(path);
                        if (Directory.Exists(directory))
                        {
                            // 但是当前没有Dll
                            var bytes = reader.GetByteArray("CurrentDllFile");
                            File.WriteAllBytes(path, bytes);
                        }
                        else
                        {
                            string localUserPath = Path.Combine(AiMoBridge.Paths.PathManager.TempDllFolder, fileName);
                            m_CurrentDllFile = localUserPath;
                            if (!File.Exists(localUserPath))
                            {
                                var bytes = reader.GetByteArray("CurrentDllFile");
                                File.WriteAllBytes(localUserPath, bytes);
                            }
                        }
                    }
                    LoadDll(out error);
                }
            }
            catch (Exception e)
            {
                error = "Cannot read AiMoScript in GH Components.\n" + e.Message;
                return false;
            }
            error = "";
            return true;
        }

        #region AttributesExtension

        public void ComponentAdditionalLayout(RectangleF bounds)
        {
            m_Executor?.CurrentInstance.ComponentAdditionalLayout(bounds);
        }
        public void ComponentAdditionalRender(GH_Canvas canvas, Graphics graphics)
        {
            m_Executor?.CurrentInstance.ComponentAdditionalRender(canvas, graphics);
        }
        public bool ComponentAdditionalIsPickRegion(PointF point)
        {
            return m_Executor?.CurrentInstance.ComponentAdditionalIsPickRegion(point) ?? false;
        }
        public GH_ObjectResponse ComponentAdditionalRespondToMouseDown(GH_Canvas sender, GH_CanvasMouseEvent e, Func<GH_Canvas, GH_CanvasMouseEvent, GH_ObjectResponse> func)
        {
            if (m_Executor == null) return func(sender, e);
            return m_Executor.CurrentInstance.ComponentAdditionalRespondToMouseDown(sender, e);
        }
        public GH_ObjectResponse ComponentAdditionalRespondToMouseMove(GH_Canvas sender, GH_CanvasMouseEvent e, Func<GH_Canvas, GH_CanvasMouseEvent, GH_ObjectResponse> func)
        {
            if (m_Executor == null) return func(sender, e);
            return m_Executor.CurrentInstance.ComponentAdditionalRespondToMouseMove(sender, e);
        }
        public GH_ObjectResponse ComponentAdditionalRespondToMouseUp(GH_Canvas sender, GH_CanvasMouseEvent e, Func<GH_Canvas, GH_CanvasMouseEvent, GH_ObjectResponse> func)
        {
            if (m_Executor == null) return func(sender, e);
            return m_Executor.CurrentInstance.ComponentAdditionalRespondToMouseUp(sender, e);
        }

        #endregion
    }
}
